
#include <Windows.h>
#include "Connection.h"
#include <string>
#include "guicon.h"
#include "Renderer.h"
#include "Line.h"
#include "BodyInfo.h"

#pragma comment(lib, "ws2_32.lib")
#pragma warning (disable : 4482)

const int N_LINES = 15;
const int N_MAXCIRCLES = 580;

Line	m_lines[N_LINES];

struct MouseClick
{
	int x;
	int y;
};


Renderer*	renderer;
int			nObjects;
int			nRopePoints;

bool		bAirjetActive;

MouseClick	dragStart;
MouseClick	dragEnd;

Connection	conn;

BodyInfo	m_circles[N_MAXCIRCLES];
Vector		m_rope[10];

Vector		mouseCoords;
Vector		airjet[2];

HWND		hwnd;

void		screenToWorldCoords(int xin, int yin, float* xout, float* yout)
{
	// if mouse X > 600, it wasn't for a ball
	if (xin > 600) {
		*xout = 100.0f;
		*yout = 100.0f;
		return;
	}

	float x = (float)xin;
	float y = (float)yin;

	float height = 600;
	y = height - y;

	// pixel coords -> [0,1]
	float xx = x / (float)600;
	float yy = y / (float)height;

	float zoom = renderer->getZoom();
	float zoom2 = zoom * 2.0f;

	// [0, 50]
	xx *= zoom2;
	yy *= zoom2;

	// [-25, 25]
	xx -= zoom;
	yy -= zoom;
	
	Vector translation = renderer->getTranslation();
	xx -= translation.GetX();
	yy -= translation.GetY();

	*xout = xx;
	*yout = yy;
}

void		addCircle(int x, int y)
{
	float xx, yy;
	screenToWorldCoords(x, y, &xx, &yy);
	if (xx > 25.0f) return;

	mouseCoords =  Vector(xx, yy);

	Message msg;
	msg.header.type = MsgType::MSG_NEWCIRCLE;
	msg.header.length = sizeof(Vector);
	msg.data = (void*)&mouseCoords;
	conn.sendMessage(msg);
}



void		addAirjet(int x0, int y0)
{
	float wx0, wy0;

	screenToWorldCoords(x0, y0, &wx0, &wy0);
	renderer->setAirjetStart(Vector(wx0, wy0));
}

void		setPause()
{
	Message msg;
	msg.header.type = MsgType::MSG_UPD_PAUSE;
	msg.header.length = 0;
	msg.data = (void*)0;
	conn.sendMessage(msg);
}

void		toggleSpring()
{
	Message msg;
	msg.header.type = MsgType::MSG_TOGGLESPRING;
	msg.header.length = 0;
	msg.data = (void*)0;
	conn.sendMessage(msg);
	renderer->bDrawSpring = !renderer->bDrawSpring;
}

void		addMultipleCircles()
{
	Message msg;
	msg.header.type = MsgType::MSG_MORECIRCLES;
	msg.header.length = 0;
	msg.data = (void*)0;
	conn.sendMessage(msg);
}



LRESULT		CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	// this is the main message handler of the system
	PAINTSTRUCT		ps;	// used in WM_PAINT
	HDC				hdc;	// handle to a device context

	TwEventWin(hwnd, msg, wparam, lparam);


	// what is the message 
	switch(msg)
	{	
	case WM_CREATE: 
		{
			// do initialization stuff here
			return(0);
		} 
	case WM_LBUTTONDOWN:
		{
			addCircle(LOWORD(lparam), HIWORD(lparam));
			break;
		}
		break;

	case WM_RBUTTONDOWN:
		{
			renderer->setAirjetActive(true);
			addAirjet(LOWORD(lparam), HIWORD(lparam));
		}
		break;

	case WM_MOUSEWHEEL:
		{
			float v = ((signed short)HIWORD(wparam) > 0) ? 0.5f : -0.5f;
			renderer->changeZoom(v);
		}
		break;

	case WM_RBUTTONUP:
		{
			renderer->setAirjetActive(false);
		}
		break;
	
	case WM_MOUSEMOVE:
		{
			if (renderer->airjetActive())
				addAirjet(LOWORD(lparam), HIWORD(lparam));
		}
		break;

	case WM_KEYDOWN:
		{
			// Handle any non-accelerated key commands
			switch (wparam)
			{
			case VK_ESCAPE:
			case VK_F12:
				PostMessage(hwnd, WM_CLOSE, 0, 0);
				return (0);
			case VK_RIGHT:
				
				break;
			case VK_SPACE:
				{
					setPause();
				}
				break;

			case 65: // A
				renderer->changePosition(+0.5f, 0);
				break;
			case 66: // B
				toggleSpring();
				break;
			case 67: // C
				addMultipleCircles();
				break;
			case 68: // D
				renderer->changePosition(-0.5f, 0);
				break;
			case 83: // S
				renderer->changePosition(0, +0.5f);
				break;
			case 87: // W
				renderer->changePosition(0, -0.5f);
				break;
			case 82: // R
				renderer->resetView();
				break;
			default:
				break;


			}
			break;
		}

	case WM_PAINT: 
		{
			// validate the window
			hdc = BeginPaint(hwnd,&ps);	 
			EndPaint(hwnd,&ps);
			return(0);
		} 

	case WM_DESTROY: 
		{
			// kill the application			
			PostQuitMessage(0);

			return(0);
		}

	default:
		break;

	} // end switch

	// process any messages that we didn't take care of 
	return (DefWindowProc(hwnd, msg, wparam, lparam));

} // end WinProc




int			createWindow(HINSTANCE hinstance)
{

	WNDCLASS	winclass;	// this will hold the class we create
			// generic window handle

	// first fill in the window class stucture
	winclass.style			= CS_HREDRAW | CS_VREDRAW;                  
	winclass.lpfnWndProc	= WindowProc;
	winclass.cbClsExtra		= 0;
	winclass.cbWndExtra		= 0;
	winclass.hInstance		= hinstance;
	winclass.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
	winclass.hCursor		= LoadCursor(NULL, IDC_ARROW);
	winclass.hbrBackground  = (HBRUSH)GetStockObject(BLACK_BRUSH);
	winclass.lpszMenuName	= NULL;
	winclass.lpszClassName	= L"WindowCreation";

	// register the window class
	if (!RegisterClass(&winclass))
		return(0);

	// create the window
	if (!(hwnd = CreateWindowA( "WindowCreation", // class
		"TITLE",	     // title
		WS_VISIBLE | WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN/*WS_OVERLAPPEDWINDOW | WS_VISIBLE*/,
		550,
		100,
		//Set the size of the window to the size of the screen 
		900,
		600,
		//GetSystemMetrics(SM_CXSCREEN),
		//GetSystemMetrics(SM_CYSCREEN),
		NULL,	   // handle to parent 
		NULL,	   // handle to menu
		hinstance,	// instance
		NULL)))	// creation parms
		return(0);

	return 1;

}


void		createGrid()
{
	// ------------------------
	m_lines[0].SetPos(0,  20.0f, 20.0f);
	m_lines[0].SetPos(1, -20.0f, 20.0f);
	m_lines[0].SetColor(0,1);
	// right vertical
	m_lines[1].SetPos(0,  20.0f, 20.0f);
	m_lines[1].SetPos(1,  20.0f, -12.0f);
	m_lines[1].SetColor(0,1);
	// left vertical
	m_lines[2].SetPos(0,  -20.0f, 20.0f);
	m_lines[2].SetPos(1,  -20.0f, -12.0f);
	m_lines[2].SetColor(0,1);
	// right step
	m_lines[3].SetPos(0,  20.0f, -12.0f);
	m_lines[3].SetPos(1,  12.0f,  -12.0f);
	m_lines[3].SetColor(0,1);
	// left step
	m_lines[4].SetPos(0,  -20.0f, -12.0f);
	m_lines[4].SetPos(1,   -12.0f, -12.0f);
	m_lines[4].SetColor(0,1);
	// bottom right
	m_lines[5].SetPos(0,  12.0f,  -12.0f);
	m_lines[5].SetPos(1,  4.0f,  -20.0f);
	m_lines[5].SetColor(0,1);
	// bottom left
	m_lines[6].SetPos(0,  -12.0f, -12.0f);
	m_lines[6].SetPos(1,   -4.0f, -20.0f);
	m_lines[6].SetColor(0,1);
	// bottom
	m_lines[7].SetPos(0,  -4.0f, -20.0f);
	m_lines[7].SetPos(1,   4.0f, -20.0f);
	m_lines[7].SetColor(0,1);
	// hor self
	m_lines[8].SetPos(0, -20, -4);
	m_lines[8].SetPos(1, -12, -4);
	m_lines[8].SetColor(0,1);
	// ver self
	m_lines[9].SetPos(0, -12, -4);
	m_lines[9].SetPos(1, -12,  4);
	m_lines[9].SetColor(0,1);
	// 
	m_lines[10].SetPos(0,  4, 4);
	m_lines[10].SetPos(1, 12, -4);
	m_lines[10].SetColor(0,1);

	// Extra lines - duplicated :/
	m_lines[11].SetPos(0, -20, -4);
	m_lines[11].SetPos(1, -12, -4);
	m_lines[11].SetColor(0,1);
	// ver self
	m_lines[12].SetPos(0, -12, -4);
	m_lines[12].SetPos(1, -12,  4);
	m_lines[12].SetColor(0,1);
	// 
	m_lines[13].SetPos(0,  4, 4);
	m_lines[13].SetPos(1, 12, -4);
	m_lines[13].SetColor(0,1);

	m_lines[14].SetPos(0, -4, 12);
	m_lines[14].SetPos(1, 4, 12);
	m_lines[14].SetColor(0,1);

}


int			WINAPI WinMain( HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow)
{

	// 3rd processor
	HANDLE threadId = GetCurrentThread();
	DWORD_PTR processAffinityMask = 8; //4;
	SetThreadAffinityMask(threadId, processAffinityMask);

	
	MSG			msg;		// generic message
	createWindow(hinstance);
	RedirectIOToConsole();
	createGrid();
	
	// 0. Renderer setup
	renderer = new Renderer();
	renderer->m_connection = &conn;
	renderer->init(hwnd, 600, 600);
	
	// 1. Connection with server setup
	conn.m_renderer = renderer;
	conn.setReceiveBuffer((void*)&m_circles);
	conn.setRopeReceiveBuffer((void*)&m_rope);
	conn.m_ropePoints = &nRopePoints;
	conn.m_objectsPerSecond = renderer->getPtrObjectsPerSec();
	conn.start(0, 0);
	conn.m_objectsReceived = &nObjects;


	// 2. Main Loop
	double freqGraphics = renderer->getGraphicsFreq();
	double delta = 1.0f / (double)freqGraphics;

	renderer->startTimer();

	double d0, d1, elapsedTime=0;
	int counter = 0;
	double timer = 0;
	double currentTime = 0;
	double avgElapsedTime = 0.0;
	double baseTime = renderer->getTime();

	bool quit = false;
	while(!quit)
	{
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
		{ 
			if (msg.message == WM_QUIT) quit = true;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		} 
		else {

			currentTime = renderer->getTime() - baseTime;
			
			if (currentTime >= delta*counter) 
			{
				d0 = renderer->getTime();
					renderer->clear();
					renderer->draw(m_rope, nRopePoints);
					renderer->draw(m_lines, N_LINES);	
					renderer->draw(m_circles, nObjects);
					renderer->present();
				d1 = renderer->getTime();
	
				elapsedTime = d1-d0;
				counter++;

				// Things to be checked after execution of each loop ------------
				// ...
			}

			avgElapsedTime += elapsedTime;
			timer += renderer->getDeltaTime();
			
			// Things to do per Second -----------------------------
			if (timer >= 1.0) {
				timer = 0.0;
				//------------------
				
				// average elapsedTime
				avgElapsedTime /= (double)freqGraphics;
				// get new value for baseTime
				baseTime = renderer->getTime();

				std::cout << "fps: " << counter << endl;

				// get current value for frequency and update delta
				freqGraphics = renderer->getGraphicsFreq();
				delta = 1.0f / (double)freqGraphics;

				avgElapsedTime = 0;
				counter = 0;
			}
		}

	} // end while


	// return to Windows like this
	return(msg.wParam);


}
